package com.gitee.wsl.time.calendar


import com.gitee.wsl.time.plus
import com.gitee.wsl.time.timestamp
import com.gitee.wsl.time.toLocalDate
import kotlinx.datetime.DayOfWeek
import kotlin.time.Duration.Companion.days


/**
 *
 *
 * This implementation of the Calendar excludes a set of days of the week. You may use it to exclude weekends for
 * example. But you may define any day of the week. By default it excludes SATURDAY and SUNDAY.
 *
 *
 * @author Juergen Donnerstag
 * @see ICalendar
 *
 * @see BaseCalendar
 */
class WeeklyCalendar (
    baseCalendar: Calendar?=null
) : AbstractCalendar(baseCalendar) {

    // An array to store the week days which are to be excluded.
    // java.util.Calendar.MONDAY etc. are used as index.
    private var excludeDays = BooleanArray(8)

    // Will be set to true, if all week days are excluded
    private var excludeAll = false

    private operator fun BooleanArray.get(dayOfWeek:DayOfWeek) = get(dayOfWeek.ordinal)

    private operator fun BooleanArray.set(dayOfWeek:DayOfWeek,value:Boolean) = set(dayOfWeek.ordinal,value)

    init {
        excludeDays[DayOfWeek.SUNDAY] = true
        excludeDays[DayOfWeek.SATURDAY] = true
        excludeAll = areAllDaysExcluded()
    }

    var daysExcluded: BooleanArray?
        /**
         *
         *
         * Get the array with the week days
         *
         */
        get() = excludeDays
        /**
         *
         *
         * Redefine the array of days excluded. The array must of size greater or equal 8. java.util.Calendar's constants
         * like MONDAY should be used as index. A value of true is regarded as: exclude it.
         *
         */
        set(weekDays) {
            if (weekDays == null) {
                return
            }

            excludeDays = weekDays
            excludeAll = areAllDaysExcluded()
        }

    private fun isDayExcluded(wday: DayOfWeek): Boolean = excludeDays[wday]

    /**
     *
     *
     * Return true, if wday (see Calendar.get()) is defined to be exluded. E. g. saturday and sunday.
     *
     */
    fun isDayExcluded(wday: Int): Boolean {
        return excludeDays[wday]
    }

    /**
     *
     *
     * Redefine a certain day of the week to be excluded (true) or included (false). Use java.util.Calendar's constants
     * like MONDAY to determine the wday.
     *
     */
    fun setDayExcluded(wday: Int, exclude: Boolean) {
        excludeDays[wday] = exclude
        excludeAll = areAllDaysExcluded()
    }

    fun setDayExcluded(wday: DayOfWeek, exclude: Boolean) = setDayExcluded(wday.ordinal, exclude)

    /**
     *
     *
     * Check if all week days are excluded. That is no day is included.
     *
     *
     * @return boolean
     */
    fun areAllDaysExcluded(): Boolean {
        return isDayExcluded(DayOfWeek.SUNDAY) && isDayExcluded(DayOfWeek.MONDAY)
                && isDayExcluded(DayOfWeek.TUESDAY) && isDayExcluded(DayOfWeek.WEDNESDAY)
                && isDayExcluded(DayOfWeek.THURSDAY) && isDayExcluded(DayOfWeek.FRIDAY)
                && isDayExcluded(DayOfWeek.SATURDAY)
    }

    /**
     *
     *
     * Determine whether the given time (in milliseconds) is 'included' by the Calendar.
     *
     *
     *
     *
     * Note that this Calendar is only has full-day precision.
     *
     */
    override fun isTimeIncluded(timeStamp: Long): Boolean {
        if (timeStamp <= 0) return false

        if (excludeAll) {
            return false
        }

        // Test the base calendar first. Only if the base calendar not already
        // excludes the time/date, continue evaluating this calendar instance.
        if (!super.isTimeIncluded(timeStamp)) {
            return false
        }

        //val cl: java.util.Calendar = createJavaCalendar(timeStamp)
        //val wday: Int = cl.get(java.util.Calendar.DAY_OF_WEEK)

        val wday = timeStamp.toLocalDate().dayOfWeek

        return !(isDayExcluded(wday))
    }

    /**
     *
     *
     * Determine the next time (in milliseconds) that is 'included' by the Calendar after the given time. Return the
     * original value if timeStamp is included. Return 0 if all days are excluded.
     *
     *
     *
     *
     * Note that this Calendar is only has full-day precision.
     *
     */
    override fun getNextIncludedTime(timeStamp: Long): Long {
        if (excludeAll) {
            return 0
        }
        var timeStamp = timeStamp

        // Call base calendar implementation first
        val baseTime: Long = super.getNextIncludedTime(timeStamp)
        if ((baseTime > 0) && (baseTime > timeStamp)) {
            timeStamp = baseTime
        }

        // Get timestamp for 00:00:00
        //val cl: java.util.Calendar = getStartOfDayJavaCalendar(timeStamp)
        //var wday: Int = cl.get(java.util.Calendar.DAY_OF_WEEK)
        var dateTime = timeStamp.toLocalDate()
        var wday = dateTime.dayOfWeek

        if (!isDayExcluded(wday)) {
            return timeStamp // return the original value
        }

        while (isDayExcluded(wday)) {
            //cl.add(java.util.Calendar.DATE, 1)
            //wday = cl.get(java.util.Calendar.DAY_OF_WEEK)
            dateTime = (dateTime + 1.days).date
            wday = dateTime.dayOfWeek
        }

        return dateTime.timestamp
    }

}

fun Calendar.Companion.weeklyCalendar(
    baseCalendar: Calendar?=null,
    config: WeeklyCalendar.() -> Unit
) = WeeklyCalendar(baseCalendar).apply { config() }